/*
 * This file is Copyright 2002-2006 Loyola University Chicago,
 * Department of Computer Science and the Emerging Technologies Laboratory.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License via
 *    http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.citep.db.hibernate.sessions;

import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.math.BigDecimal;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;


import com.citep.business.Account;
import com.citep.business.AccountCategory;
import com.citep.business.AccountTypes;
import com.citep.business.sessions.*;

import com.citep.db.hibernate.AccountCategoryDAO;
import com.citep.db.hibernate.AccountDAO;

public class AccountSessionImpl extends HibernateDaoSupport implements AccountSession {
	
	
	public Account createAccount(String name, String description,AccountTypes type, BigDecimal balance, Date startPeriod, Date endPeriod, boolean enabled, AccountCategory category) throws ValidationException {
		return this.createAccount(name, description, type, balance, startPeriod, endPeriod, enabled, category, null);
	}
	
	public Account createAccount(String name, String description,AccountTypes type, BigDecimal balance, Date startPeriod, Date endPeriod, boolean enabled, AccountCategory category, Account parent) throws ValidationException {
		
		Account account = new AccountDAO();
	
		account.setParent(parent);
		
		account.setName(name);
		account.setDescription(description);
		account.setType(type);
		
		//at the beginning, the current balance = starting balance
		account.setCurrentBalance(balance);
		//TODO: transfer from an equity account to this account for the initial balance
		
		account.setStartPeriod(startPeriod);
		account.setEndPeriod(endPeriod);
		account.setEnabled(enabled);
		account.setAccountCategory(category);
		
		return createAccount(account);
	}

	public Account createAccount(Account account) throws ValidationException {

		//make sure there are no other accounts with the same name
		List<Account> list = getHibernateTemplate().find("select account from AccountDAO as account where account.name = ?",account.getName());
		if(list.size() > 0) {
			
			for(Account stored:list) {
				if(stored.equals(account)) {
					ValidationException validationError = new ValidationException();
					validationError.addField("Account.name", "There is already an account with that name: " + account.getName());
					throw validationError;
				}
			}
			
		}
		
		account.setLastModified(new Date());
		getHibernateTemplate().save(account);
		return account;
	}
	
	public Account getAccount(int id) {
		return (Account)getHibernateTemplate().load(AccountDAO.class,new Integer(id));
	}
	

	public void updateAccount(Account account) throws ValidationException {
		account.setLastModified(new Date());
		getHibernateTemplate().update(account);
	}
	
	public void deleteAccount(int id) {
		Account account = getAccount(id);
		deleteAccount(account);
	}
	
	public void deleteAccount(Account account) {
		getHibernateTemplate().delete(account);
	}
	
	public void enableAccount(int id) {
		Account account = getAccount(id);
		account.setEnabled(true);
		account.setLastModified(new Date());
		updateAccount(account);
	}
	
	public void disableAccount(int id) {
		Account account = getAccount(id);
		account.setEnabled(false);
		account.setLastModified(new Date());
		updateAccount(account);
	}

	public List<Account> getAccounts(final String name) {
		List list = getHibernateTemplate().find("select account from AccountDAO as account where account.name = ?",name);
		return (List<Account>)list;
	}
	
	public List<Account> getAccounts(final int offset, final int limit) {
		List result = getHibernateTemplate().executeFind(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				Criteria c = session.createCriteria(AccountDAO.class);
				
				if(limit > 0)
					c.setMaxResults(limit);
				
				c.setFirstResult(offset);
				c.addOrder(Order.asc("name"));
				
				
				List l = c.list();
				return c.list();
			}
		});
		
		return (List<Account>)result;
	}
	
	private List<Account>getTopLevelAccounts() {

		List list = getHibernateTemplate().find("select account from AccountDAO as account where account.parent = null");
		
		return (List<Account>)list;
	}
	
	/*
	 * Returns the first account that does not have a parent. Usually
	 * you only want one of these in your application, acting as the root
	 * of the whole account tree
	 * @see com.citep.business.sessions.AccountSession#getRoot()
	 */
	public Account getRoot() {
		
		Account root = null;
		for(Account account:getTopLevelAccounts())
			if("root".equals(account.getName()))
				root = account;
		return root;
	}
	
	public Date getAccountTreeLastModified() {
		List result = getHibernateTemplate().executeFind(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				Criteria c = session.createCriteria(AccountDAO.class);
				c.setMaxResults(1);
				c.addOrder(Order.desc("lastModified"));
				
				return c.list();
			}
		});
		

		if(result == null || result.size() < 1)
			return null;
		
		Account account = (Account)result.get(0);
		return account.getLastModified();
		
	}

	public AccountCategory getAccountCategory(String name) {
		List list = getHibernateTemplate().find("select category from AccountCategoryDAO as category where category.name = ?", name);
		if(list.size() == 0)
			return null;
		else
			return (AccountCategory)list.get(0);
	}
	
	public Account getAccountTree(final int accountId) {
		return (Account)getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session) throws HibernateException, SQLException {
						Account root = (Account)session.load(AccountDAO.class,new Integer(accountId));

						
						java.util.Stack<Account>pending = new java.util.Stack<Account>();
						pending.push(root);
						
						while(pending.size() > 0) {
							Account current = pending.pop();
							if(current.hasChildren())
								for(Account child:current.getChildren())
									pending.push(child);
						}
						
						return root;
					}
					
				});				
	}
	
	
	public AccountCategory getAccountCategory(int id) {
		return (AccountCategory)getHibernateTemplate().load(AccountCategoryDAO.class,new Integer(id));
	}
	
	public AccountCategory createAccountCategory(AccountCategory category) throws ValidationException {

		// Make sure that there account category name is unique
		List<AccountCategory> list = (List<AccountCategory>)getHibernateTemplate().find("select category from AccountCategoryDAO as category where category.name = ?", new String [] {category.getName()});

		
		if(list.size() > 0) {
			ValidationException validationError = new ValidationException();
			validationError.addField("AccountCategory.name", "There is already an account category with that name: " + category.getName());
			
			throw validationError;
		}
		
		getHibernateTemplate().save(category);
		return category;
	}
	
	public AccountCategory createAccountCategory(String name, String description) throws ValidationException {

		AccountCategory accountCategory = new AccountCategoryDAO();
		accountCategory.setName(name);
		accountCategory.setDescription(description);
		accountCategory.setEnabled(true);
		
		return createAccountCategory(accountCategory);
	}
	
	public void updateAccountCategory(AccountCategory category) throws ValidationException {
		getHibernateTemplate().update(category);
	}
	
	public void deleteAccountCategory(int id) {
		AccountCategory accountCategory = getAccountCategory(id);
		deleteAccountCategory(accountCategory);
	}
	
	public void deleteAccountCategory(AccountCategory category) {
		getHibernateTemplate().delete(category);
	}
	
	public void enableAccountCategory(int id) {
		AccountCategory accountCategory = getAccountCategory(id);
		accountCategory.setEnabled(true);
		updateAccountCategory(accountCategory);
	}
	
	public void disableAccountCategory(int id) {
		AccountCategory accountCategory = getAccountCategory(id);
		accountCategory.setEnabled(false);
		updateAccountCategory(accountCategory);
	}
	
	public List<AccountCategory> getAccountCategories() {
		return getHibernateTemplate().find("from AccountCategoryDAO");
	}
	
	public Set<Account>getAccountCategoryAssociatedAccounts(final AccountCategory category) {
		Set result = (Set)getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session) throws HibernateException, SQLException {
						AccountCategoryDAO accountCategory = (AccountCategoryDAO)category;
						//AccountCategoryImpl accountCategory = (AccountCategoryImpl) accountCategoryIf;
						session.lock(accountCategory, LockMode.NONE);
						
						Hibernate.initialize(accountCategory.getAccounts());
						return accountCategory.getAccounts();
					}
					
				});
		return (Set<Account>)result;
	}
	
	public void flush() {
		this.getHibernateTemplate().flush();
	}
}
